package abaco.symbol.operation;

import java.awt.GridLayout;
import java.util.HashMap;
import java.util.TreeSet;

import javax.swing.Box;

import abaco.Symbol;
import abaco.gui.Panel;
import abaco.symbol.Operation;
import abaco.symbol.Text;

public abstract class Binary extends Operation {
	protected static final String BASENAME = "operation-binary-";

	protected Integer minParameters;
	protected Integer maxParameters;
	protected String latexOperator;

	protected String lastChildNameAdded;

	protected void setupChildren() {
		Integer max = null == minParameters ? 2 : minParameters;
		for (int i = 0; i < max; i++) {
			setSymbolChild(new Text());
		}
	}

	@Override
	public Symbol buildPanel(Panel panel) {
		checkChildren(true);

		Integer childrenSize = children.size();
		panel.setLayout(new GridLayout(1, (childrenSize * 2) -1));

		Panel childPanel;
		Integer count = 1;
		for (String name : getSortedChildrenNames()) {
			childPanel = getChildPanel(name, false);
			panel.add(childPanel);
			if (count < childrenSize) {
				panel.add(Box.createHorizontalStrut(10));
			}
			count++;
		}

		return this;
	}

	@Override
	public String toLatex() {
		if (!checkChildren(false)) {
			return UNDEFINED_LATEX;
		}
		// build latex
		StringBuilder latex = new StringBuilder();
		latex.append("{");
		for (String name : getSortedChildrenNames()) {
			latex.append(children.get(name).toLatex());
			if (null != latexOperator) {
				latex.append(latexOperator);
			}
		}
		latex.append("}");
		return latex.toString();
	}

	protected Boolean checkChildren(Boolean throwException) {
		Integer size = children.size();
		if ((null == minParameters || size >= minParameters) &&
			(null == maxParameters || size <= maxParameters)
		) {
			return true;
		}
		if (throwException) {
			//throw new UnexpectedException("Invalid argument size");
		}
		return false;
	}

	@Override
	public Binary setSymbolChild(Symbol child, String name) {
		String number = name.substring(BASENAME.length());
		if (!name.startsWith(BASENAME) || !number.matches("^\\d$")) {
			throw new IllegalArgumentException();
		}
		lastChildNameAdded = name;
		super.setSymbolChild(child, name);
		return this;
	}

	/**
	 * Add a child at the end of the list.
	 */
	public Binary setSymbolChild(Symbol child) {
		String name = getNextAvailableChildName();
		lastChildNameAdded = name;
		setSymbolChild(child, name);
		return this;
	}

	/**
	 * Add a child in a specific position. CAUTION! Children positioned after
	 * desired position will be renamed.
	 */
	public Binary setSymbolChild(Symbol child, Integer position) {
		// check negative position
		if (position < 0) {
			return this;
		}

		// check position overflow, add child at the end of the list
		TreeSet<String> names = getSortedChildrenNames();
		Integer lastPosition = names.size() - 1;
		if (-1 == lastPosition || position > lastPosition) {
			return setSymbolChild(child);
		}

		// create a children backup, when next step starts moving children, some
		// values may be replaced, so it's necessary fetch original values
		HashMap<String, Symbol> backup = new HashMap<String, Symbol>(children);

		// no magic, just insert a child in a specific position.
		// CAUTION! note that name ending may not represent position desired
		// note also that when the new child is added, this one may replace some
		// existent child, due that some children may be renamed
		Integer curPos = 0, number;
		String newName = "";
		Boolean moveForward = false;
		for (String name : names) {
			if (curPos == position) {
				// try add element one number before, if this fails, current
				// name is picked
				number = Integer.parseInt(name.substring(BASENAME.length())) - 1;
				String prev = BASENAME + number.toString();
				newName = number >= 0 && !children.containsKey(prev) ? prev : name;
				setSymbolChild(child, newName);
				moveForward = true;
			}
			if (moveForward) {
				if (backup.get(name).equals(children.get(name))) {
					// forward moving not required anymore
					break;
				}
				number = Integer.parseInt(name.substring(BASENAME.length())) + 1;
				setSymbolChild(backup.get(name), BASENAME + number.toString());
			}
			curPos++;
		}
		lastChildNameAdded = newName;

		return this;
	}

	/**
	 * Returns last name like "operation-binary-5", but take care about ending
	 * number, this doesn't means children position, it's just a name.
	 */
	public String getLastChildNameAdded() {
		return lastChildNameAdded;
	}

	protected String getNextAvailableChildName() {
		TreeSet<String> set = getSortedChildrenNames();
		if (0 == set.size()) {
			return BASENAME + "0";
		}
		String number = set.last().substring(BASENAME.length());
		Integer next = Integer.parseInt(number) + 1;
		return BASENAME + next.toString();
	}

	protected TreeSet<String> getSortedChildrenNames() {
		return new TreeSet<String>(children.keySet());
	}
}